`include "../../include/cpu_defines.sv"

module CP0IssueQueue #(
	parameter BUFFER_SIZE=4
)(
	input logic cpu_clk,
	input logic cpu_rst_n,
	input logic clear,
	input logic stall,
	input logic inst1_en,
	input logic inst2_en,
	input logic [`CP0OP_BUS] inst1_op, // read from cp0 or write to cp0. 0 is read
	input logic [`CP0OP_BUS] inst2_op,
	input logic inst1_rs_ready,
	input logic inst2_rs_ready,
	input logic inst1_rt_ready,
	input logic inst2_rt_ready,
	input logic [`EXC_CODE_BUS] inst1_exccode,
	input logic [`EXC_CODE_BUS] inst2_exccode,
	input logic [5: 0] inst1_rs,
	input logic [5: 0] inst2_rs,
	input logic [5: 0] inst1_rd,
	input logic [5: 0] inst2_rd,
	input logic [5: 0] inst1_rob_index,
	input logic [5: 0] inst2_rob_index,
	input logic [4: 0] inst1_cacheop,
	input logic [4: 0] inst2_cacheop,
	input logic [31: 0] inst1_src2,
	input logic [31: 0] inst2_src2,
	input logic wb_en1,
	input logic wb_en2,
	input logic wb_en3,
	input logic [5: 0] wb_rd1,
	input logic [5: 0] wb_rd2,
	input logic [5: 0] wb_rd3,
	input logic retire_en1,
	input logic retire_en2,
	input logic retire_cp0_en1,
	input logic retire_cp0_en2,
	input logic [5: 0] retire_rob_index1,
	input logic [5: 0] retire_rob_index2,
	input logic [31: 0] prf_data,
	input logic [31: 0] cp0_data,
	input logic cp0_receive,
	input logic [31: 0] rob_pc,
	input logic [31: 0] rob_pc4,
	// input logic [2: 0] soft_en,

	output logic [5: 0] prf_rt,
	output logic [5: 0] cp0_rt,
	output logic cp0_end,
	output logic cp0_we,
	// output logic cp0_soft_int,
	output logic we_inst,
	output logic [31: 0] cp0_soft_addr,

	output logic [5: 0] next_rd,
	output logic [5: 0] cp0_rd,
	output logic [31: 0] cp0_wdata,
	output logic [`CP0OP_BUS] cp0_op,

	output logic tlb_inst_en,
	output logic [4: 0] tlb_type,

	input logic icache_ready,
	input logic dcache_ready,
	output logic cache_valid,
	output logic [4: 0] cacheop,
	output logic [31: 0] cache_addr,
	//送往CDB写回用
	output logic [5: 0] 		out_rd,
	output logic [5: 0] 		out_rob_index,
	output logic [31: 0] 		out_data,
	output logic [`EXC_CODE_BUS]out_exccode,
	output logic dis_cp0_stall_req,

	output logic waiting // 当前wait指令将退休,因为中断需要等到指令退休才执行，而wait将导致没有指令退休而无法中断
);
/*
	我们这样设计TLB指令的数据通路：
		TLB指令会按序的进入extra_buffer，并且会进入write_buffer,但是不会进行映射。
		TLB指令仅需需要我们保证他的顺序即可，其自身不携带数据，只要TLB流入了CP0中，其读写就会自动完成。
		如果TLB的顺序是不对的，比如
			1. mtc0 （entryhi = 1）
			2. tlbw	（entryhi = 2）
		如果直接让TLB插队，那么最后entryhi就是1了，显然不正确。
		这里还有就是如果分支预测失败，会错误的执行TLB
		TLB指令后的指令实际上不用考虑其正确性，比如
			1. tlbw （entryhi = 1）
			2. mfc0 （read enryhi）
		即使出现了这种情况，也没关系，因为TLB退休的时候会刷新流水线，mfc0这条指令会进行重取，
		等到重取来了的时候，entryhi的数据早就由tlbw写入cp0了。

		对于cache指令:
		1. cacheop是指令定义中的op段,src2 + pr(rs)是进行cache操作的虚拟地址
		2. 只有当目前ICache和DCache没有进行Cache操作时才可以发送(由icache_free, dcache_free指示)
*/
	typedef struct packed {
		logic [`EXC_CODE_BUS] exccode;
		logic [`CP0OP_BUS] op; // 1: mtc0 . 0: mfc0
		logic [5: 0] rd;
		logic [5: 0] rob_index;
		logic en;
		logic [5:0] rs;
		logic rs_ready;
		logic icache_op;
		logic dcache_op;
		logic [4: 0] cacheop;
		logic [31: 0] src2;
	} cp0_extra_t;
	cp0_extra_t buffer_extra[BUFFER_SIZE-1 : 0];
	logic [31: 0] buffer_cache_addr[7: 0];
	// [current_index 		tail] ->  
	logic [$clog2(BUFFER_SIZE)-1: 0] current_index, tail;
	logic write_buffer_tail_is_tlb, tlbr, tlbp, tlbwi, tlbwr, is_tlb;
	logic cache_ready_next, icache_free, dcache_free, retire_icache_free, retire_dcache_free;
	logic [31: 0] prf_data_next, src2_next, cal_cache_addr;

	logic  buffer_extra_all_en;
	assign buffer_extra_all_en = buffer_extra[0].en & buffer_extra[1].en & buffer_extra[2].en & buffer_extra[3].en;

	assign dis_cp0_stall_req = buffer_extra_all_en & (inst1_en | inst2_en);
	assign prf_rt = buffer_extra[tail].rs;
	assign cp0_rt = buffer_extra[tail].rs;
	//cp0_end 代表可以把extra的tail放到write_buffer里面
	// 如果是wait指令则进行流入总线从而阻塞流水线
	assign cp0_end = buffer_extra[tail].en && buffer_extra[tail].rs_ready && (buffer_extra[tail].op != `CP0OP_WAIT) && (!buffer_extra[tail].icache_op || buffer_extra[tail].icache_op && icache_free) && (!buffer_extra[tail].dcache_op || buffer_extra[tail].dcache_op && dcache_free);
	assign next_rd = (buffer_extra[tail].op == `CP0OP_MFC0) ? buffer_extra[tail].rd : 0;

	wire move_to_wirte_buffer_en = cp0_end & cp0_receive & (buffer_extra[tail].op != `CP0OP_MFC0);
	logic [2: 0] write_buffer_head, write_buffer_tail, write_buffer_head_next;
	logic [51: 0] douta, doutb;
	logic [7: 0] write_buffer_en;
	logic [63: 0] reg_mapping_en;
	logic [2: 0] mapping_index, mapping_compare_index;

	SDPRAM #(
		.WORD_WIDTH(52),
		.DEPTH(8),
		.ADDR_WIDTH(3)
	) write_buffer(
		.clk(cpu_clk),
		.ena(1'b1),
		.wea(move_to_wirte_buffer_en),
		.addra(buffer_extra[tail].op == `CP0OP_MFC0 ? mapping_index : write_buffer_head),
		.dina({buffer_extra[tail].cacheop, buffer_extra[tail].op, buffer_extra[tail].rob_index, buffer_extra[tail].rd, prf_data}),
		.enb(1'b1),
		.addrb(write_buffer_tail),
		.douta(douta),
		.doutb(doutb)
	);

	// 32-64为cp1
	SDPRAM #(
		.WORD_WIDTH(3),
		.DEPTH(64),
		.ADDR_WIDTH(6)
	) reg_mapping_buffer(
		.clk(cpu_clk),
		.ena(1'b1),
		.wea(cp0_end & cp0_receive & (buffer_extra[tail].op == `CP0OP_MTC0)),
		.addra((buffer_extra[tail].op != `CP0OP_MFC0) ? buffer_extra[tail].rd : buffer_extra[tail].rs),
		.dina(write_buffer_head),
		.douta(mapping_index),
		.enb(1'b1),
		.addrb(doutb[36: 32]),
		.doutb(mapping_compare_index)
	);
	//如果write_buffer的头部的指令en且退休，传入cp0
	assign cp0_rd 		= doutb[37: 32];
	assign cp0_wdata 	= doutb[31: 0];
	assign cp0_op 		= doutb[46: 44];
    assign tlbwr = cp0_op == `CP0OP_TLBWR;
    assign tlbwi = cp0_op == `CP0OP_TLBWI;
    assign tlbr  = cp0_op == `CP0OP_TLBR;
    assign tlbp  = cp0_op == `CP0OP_TLBP;
	assign tlb_type = {tlbwr, tlbwi, tlbr, tlbp};
    assign is_tlb = tlbwi | tlbwr | tlbr | tlbp;
	assign cp0_we = ~clear & write_buffer_en[write_buffer_tail] & (doutb[46: 44] == `CP0OP_MTC0) &
					(retire_en1 & (retire_rob_index1 == doutb[43: 38]) | 
					 retire_en2 & (retire_rob_index2 == doutb[43: 38]));
	assign write_buffer_tail_is_tlb = ~clear & write_buffer_en[write_buffer_tail] & 
					(retire_cp0_en1 & (retire_rob_index1 == doutb[43: 38]) | 
					retire_cp0_en2 & (retire_rob_index2 == doutb[43: 38]));
	assign cache_valid = ~clear & write_buffer_en[write_buffer_tail] & (doutb[46: 44] == `CP0OP_CACHE) &
					(retire_en1 & (retire_rob_index1 == doutb[43: 38]) | 
					 retire_en2 & (retire_rob_index2 == doutb[43: 38]));
	assign cacheop = doutb[51: 47];
	assign cache_addr = buffer_cache_addr[write_buffer_tail];
	assign tlb_inst_en = write_buffer_tail_is_tlb;
	assign waiting = ~clear && buffer_extra[tail].en && (retire_rob_index1 == buffer_extra[tail].rob_index || retire_rob_index2 == buffer_extra[tail].rob_index) && buffer_extra[tail].op == `CP0OP_WAIT;
	always_ff @(posedge cpu_clk)begin
		we_inst <= retire_en2 & (retire_rob_index2 == doutb[43: 38]);
		cp0_soft_addr <= (retire_en2 & (retire_rob_index2 == doutb[43: 38]) ? rob_pc4 : rob_pc) + 8;
	end

	integer  i = 0;
	logic inst1_is_tlb, inst2_is_tlb;
	assign inst1_is_tlb = 	(inst1_op == `CP0OP_TLBP) |
							(inst1_op == `CP0OP_TLBR) |
							(inst1_op == `CP0OP_TLBWI)|
							(inst1_op == `CP0OP_TLBWR);
	assign inst2_is_tlb = 	(inst2_op == `CP0OP_TLBP) |
							(inst2_op == `CP0OP_TLBR) |
							(inst2_op == `CP0OP_TLBWI)|
							(inst2_op == `CP0OP_TLBWR);
	
	Adder adder_cache_addr(
		.A(prf_data_next),
		.B(src2_next),
		.ADD(1),
		.S(cal_cache_addr)
	);
	always_ff @(posedge cpu_clk)begin
		prf_data_next <= prf_data;
		src2_next <= buffer_extra[tail].src2;
		write_buffer_head_next <= write_buffer_head;
		cache_ready_next <= cp0_end && cp0_receive && (buffer_extra[tail].icache_op || buffer_extra[tail].dcache_op);
		if(cpu_rst_n == 1'b0)begin
			retire_icache_free <= 1'b1;
			retire_dcache_free <= 1'b1;
		end
		else begin
			retire_icache_free <= cache_valid && !cacheop[0]? 1'b0 : icache_ready ? 1'b1 : retire_icache_free;
			retire_dcache_free <= cache_valid && cacheop[0] ? 1'b0 : dcache_ready ? 1'b1 : retire_dcache_free;
		end
		if(cpu_rst_n == 1'b0 || clear)begin
			for(i = 0; i < BUFFER_SIZE ; i ++) begin
				buffer_extra[i] <= 0;
				buffer_cache_addr[i] <= 0;
			end
			current_index <= 0;
			tail <= 0;
			out_rd <= 0;
			out_rob_index <= 0;
			out_data <= 0;
			out_exccode <= `EXC_NONE;
			write_buffer_head <= 0;
			write_buffer_tail <= 0;
			write_buffer_en <= 0;
			reg_mapping_en <= 0;
			icache_free <= cpu_rst_n == 1'b0 || icache_ready ? 1'b1 : retire_icache_free;
			dcache_free <= cpu_rst_n == 1'b0 || dcache_ready ? 1'b1 : retire_dcache_free;
			write_buffer_en <= 0;
		end
		else begin
			//只有mfc0不放到write_buffer里面去
			write_buffer_head <= write_buffer_head + move_to_wirte_buffer_en;
			write_buffer_tail <= write_buffer_tail + cp0_we + cache_valid;
			//write_buffer里的一项已经写回了，从map里面删除映射, tlb不用删除映射
			if(cp0_we && mapping_compare_index == write_buffer_tail && doutb[46:44] == `CP0OP_MTC0)begin
				reg_mapping_en[doutb[37: 32]] <= 1'b0;
			end
			//往write_buffer里写入一项
			if(move_to_wirte_buffer_en)begin
				write_buffer_en[write_buffer_head] <= 1'b1;
				if(buffer_extra[tail].op == `CP0OP_MTC0) begin
					reg_mapping_en[buffer_extra[tail].rd[4:0]] <= 1'b1;
				end
			end
			//从write_buffer里取出来一项, 在遇到tlb项的时候也直接拿出来
			if(cp0_we | write_buffer_tail_is_tlb | cache_valid)begin
				write_buffer_en[write_buffer_tail] <= 1'b0;
			end
			icache_free <= cp0_end && cp0_receive && buffer_extra[tail].icache_op ? 1'b0 : icache_ready ? 1'b1 : icache_free;
			dcache_free <= cp0_end && cp0_receive && buffer_extra[tail].dcache_op ? 1'b0 : dcache_ready ? 1'b1 : dcache_free;
			if(cache_ready_next)begin
				buffer_cache_addr[write_buffer_head_next] <= cal_cache_addr;
			end

			current_index <= current_index + (~stall & (inst1_en | inst2_en));
			tail <= tail + (cp0_end & cp0_receive);
			//buffer_extra的头部取出来，放到cdb总线上
			if(cp0_end && cp0_receive)begin
				out_rd 			<= (buffer_extra[tail].op == `CP0OP_MFC0) ? buffer_extra[tail].rd : 0;
				out_rob_index 	<=  buffer_extra[tail].rob_index;
				//从write_buffer或者cp0取值
				out_data 		<= reg_mapping_en[buffer_extra[tail].rs] ? douta[31: 0] : cp0_data;
				out_exccode 	<= buffer_extra[tail].exccode;
				buffer_extra[tail].en <= 1'b0;
			end
			//往buffer_extra里写入数据
			if(~stall & (inst1_en))begin
				buffer_extra[current_index].exccode 	<= inst1_exccode;
				buffer_extra[current_index].op 			<= inst1_op;
				buffer_extra[current_index].rd 			<= inst1_rd;
				buffer_extra[current_index].rob_index 	<= inst1_rob_index;
				buffer_extra[current_index].en 			<= 1'b1;
				buffer_extra[current_index].rs_ready 	<= inst1_rs_ready & (inst1_op == `CP0OP_CACHE) | inst1_rt_ready & (inst1_op == `CP0OP_MTC0) | (inst1_is_tlb) | (inst1_op == `CP0OP_MFC0);
				buffer_extra[current_index].rs 			<= inst1_rs;
				buffer_extra[current_index].icache_op	<= inst1_op == `CP0OP_CACHE && !inst1_cacheop[0];
				buffer_extra[current_index].dcache_op 	<= inst1_op == `CP0OP_CACHE && inst1_cacheop[0];
				buffer_extra[current_index].cacheop 	<= inst1_cacheop;
				buffer_extra[current_index].src2 		<= inst1_src2;
			end
			if(~stall & inst2_en)begin
				buffer_extra[current_index].exccode 	<= inst2_exccode;
				buffer_extra[current_index].op 			<= inst2_op;
				buffer_extra[current_index].rd 			<= inst2_rd;
				buffer_extra[current_index].rob_index 	<= inst2_rob_index;
				buffer_extra[current_index].en 			<= 1'b1;
				buffer_extra[current_index].rs_ready 	<= inst2_rs_ready & (inst2_op == `CP0OP_CACHE) | inst2_rt_ready & (inst2_op == `CP0OP_MTC0) | (inst2_is_tlb) | (inst2_op == `CP0OP_MFC0);
				buffer_extra[current_index].rs 			<= inst2_rs;
				buffer_extra[current_index].icache_op	<= inst2_op == `CP0OP_CACHE && !inst2_cacheop[0];
				buffer_extra[current_index].dcache_op 	<= inst2_op == `CP0OP_CACHE && inst2_cacheop[0];
				buffer_extra[current_index].cacheop 	<= inst2_cacheop;
				buffer_extra[current_index].src2 		<= inst2_src2;
			end
			//唤醒buffer
			for(int i=0; i<BUFFER_SIZE; i++)begin
				if(buffer_extra[i].en)begin
					buffer_extra[i].rs_ready <= buffer_extra[i].rs_ready | 
												(wb_en1 && wb_rd1 == buffer_extra[i].rs) | 
												(wb_en2 && wb_rd2 == buffer_extra[i].rs) | 
												(wb_en3 && wb_rd3 == buffer_extra[i].rs);
				end
			end
		end
	end
endmodule